#include "include/types.h"
#include "include/string.h"
#include "include/segment.h"
#include "include/interrupt.h"

struct gate_desc { // Gate descriptor(Interrupt descriptor)
  uint16_t offset_low; // var
  uint16_t segment;
  uint16_t ist : 3, zero : 5, type : 4, zero2 : 1, dpl : 2, p : 1; // type var
  uint16_t offset_middle; // var
  uint32_t offset_high; // var
  uint32_t reserved;
} __attribute__((packed));

struct gate_desc idt_table[256]; // Interrupt descriptor table

#define GATE_INTERRUPT 0xe /* Interrupt type: 0xe|Interrupt  */
#define GATE_EXCEPTION 0xf /* Interrupt type: 0xf|Exception */
#define COUNTER (1193181 / 100) /* Counter = Clock (crystal oscillator) interrupt frequency divided by clock signal frequency 100Hz */

/**
 * set interrupt descriptor function
 * 
 * @param index Interrupt number
 * @param addr Interrupt handling function address
 * @param type Interrupt type: 0xe|Interrupt, 0xf|Exception
 */
static void set_gate(unsigned char index, unsigned long addr, char type) {
  struct gate_desc* desc = &idt_table[index];
  memset(desc, 0, sizeof(struct gate_desc)); // init 0
  desc->segment = KERNEL_CS; // kernel code segment
  desc->offset_low = (uint16_t)addr; // 2B
  desc->offset_middle = (uint16_t)(addr >> 16); // 2B
  desc->offset_high = (uint32_t)(addr >> 32); // 4B, total: 8B
  desc->dpl = 0;
  desc->type = type;
  desc->p = 1; // existence bit
}

void interrupt_init() {
  // [14] In the interrupt descriptor, bind the page fault interrupt handling function to the 0xe (14) interrupt number
  set_gate(0xe, (unsigned long)&pf_handler, GATE_EXCEPTION);
  // [32] In the interrupt descriptor, bind the clock interrupt handling function to the 0x20 (32) interrupt number
  set_gate(0x20, (unsigned long)&timer_handler, GATE_INTERRUPT);
  // [33] In the interrupt descriptor, bind the keyboard interrupt handling function to the 0x21 (33) interrupt number
  set_gate(0x21, (unsigned long)&kb_handler, GATE_INTERRUPT);
}

void init_8254() {
  __asm__ ("outb %%al, $0x43"::"a"(0x36)); // Specify counter 0, write control word to port 0x43
  __asm__ ("outb %%al, $0x40"::"a"(COUNTER & 0xff)); // Write the low 8-bits count value of counter 0 to port 0x40
  __asm__ ("outb %%al, $0x40"::"a"(COUNTER >> 8)); // Write the high 8-bits count value of counter 0 to port 0x40
}